package com.boot.business.common.entity;


import lombok.Getter;
import lombok.Setter;

import org.hibernate.annotations.GenericGenerator;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.AbstractBeanFactory;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Persistable;
import org.springframework.format.annotation.DateTimeFormat;

import com.boot.business.common.context.ApplicationContextHolder;
import com.boot.business.common.util.BeanCopier;
import com.boot.business.common.util.Pages;
import com.boot.business.common.util.ToString;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import javax.persistence.*;

import java.io.Serializable;
import java.util.*;

@MappedSuperclass
@Getter
@Setter
public abstract class AbstractEntity implements Serializable, Persistable<String> {

	private static final long serialVersionUID = -1574220111000426936L;

	private static volatile AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor = null;

    private static boolean inited = false;

    @Id
	@GenericGenerator(name = "systemUUID", strategy = "uuid")
	@GeneratedValue(generator = "systemUUID")
    @Column(name = "id", columnDefinition = "varchar(32) NOT NULL DEFAULT '0' comment '商户id'")
    protected String id;

    @Column(name = "merchant_id", columnDefinition = "varchar(11) NOT NULL DEFAULT '0' comment '商户id'")
    protected String merchantId = "0";

    @Column(name = "create_time", insertable = false, updatable = false,columnDefinition = " timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    private Date createTime;

    @Column(name = "update_time", insertable = false, updatable = false,columnDefinition = "timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间'")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    private Date updateTime;

    @Version
    @Column(name = "version", columnDefinition = "smallint(6) unsigned NOT NULL DEFAULT '0' COMMENT '版本号'")
    protected Short version = 0;

    public AbstractEntity() {
        if (inited) {
            autowire();
        }
    }

    private void autowire() {
        if (autowiredAnnotationBeanPostProcessor == null) {
            synchronized (AbstractEntity.class) {
                if (autowiredAnnotationBeanPostProcessor == null) {
                    List<BeanPostProcessor> beanPostProcessors = ((AbstractBeanFactory) (((AbstractApplicationContext) ApplicationContextHolder
                            .get()).getBeanFactory())).getBeanPostProcessors();
                    for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
                        if (beanPostProcessor instanceof AutowiredAnnotationBeanPostProcessor && beanPostProcessor
                                .getClass().getName().contains("AutowiredAnnotationBeanPostProcessor")) {
                            autowiredAnnotationBeanPostProcessor = (AutowiredAnnotationBeanPostProcessor) beanPostProcessor;
                        }
                    }
                }
            }
        }
        autowiredAnnotationBeanPostProcessor.postProcessPropertyValues(null, null, this, getClass().getName());
    }

    public static void inited() {
        inited = true;
    }

    /**
     * 从DTO拷贝属性到领域对象
     * <p>
     * 拷贝策略为：1.忽略DTO中的null 2. 忽略DTO属性不兼容
     * </p>
     * @param dto 传输对象
     */
    public void from(Object dto) {
        BeanCopier.copy(dto, this, BeanCopier.CopyStrategy.IGNORE_NULL, BeanCopier.NoMatchingRule.IGNORE);
    }

    /**
     * 从DTO拷贝属性到领域对象
     * <p>
     *     拷贝策略为：1.忽略DTO中的null 2. 忽略DTO属性不兼容 3.忽略DTO中的某些属性
     * </p>
     * @param dto  传输对象
     * @param ingoreProperties 忽略拷贝的属性,一般为DTO中的非基本类型的属性,举例:UserDto中包含了DepartmentDto,DepartmentDto 拷贝时需要忽略
     */
    public void from(Object dto,String ...ingoreProperties) {
        BeanCopier.copy(dto, this, BeanCopier.CopyStrategy.IGNORE_NULL, BeanCopier.NoMatchingRule.IGNORE,ingoreProperties);
    }

    public void fromContainNUll(Object dto) {
        BeanCopier.copy(dto, this, BeanCopier.CopyStrategy.CONTAIN_NULL, BeanCopier.NoMatchingRule.EXCEPTION);
    }
     /**
     * 通过反射拷贝对象
     * <p>
     *     通过反射创建对象，然后拷贝当前对象到反射对象中，拷贝策略为：1.忽略当前对象的null 2. 忽略当前对象的属性不兼容
     * </p>
     * @param <T>  反射类
      *return  t 拷贝过后的反射对象
     */
    public <T> T to(Class<T> clazz) {
        try {
            T t = clazz.newInstance();
            BeanCopier.copy(this, t, BeanCopier.CopyStrategy.IGNORE_NULL, BeanCopier.NoMatchingRule.IGNORE);
            return t;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
      /**
     * 通过反射拷贝对象
     * <p>
     *     通过反射创建对象，然后拷贝当前对象到反射对象中，拷贝策略为：1.忽略当前对象的null 2. 忽略当前对象的属性不兼容 3.忽略DTO中的某些属性
     * </p>
     * @param <T>  反射类
     * @param ingoreProperties 忽略拷贝的属性,一般为DTO中的非基本类型的属性,举例:UserDto中包含了DepartmentDto,DepartmentDto 拷贝时需要忽略
      *return  t 拷贝过后的反射对象
     */
    public <T> T to(Class<T> clazz,String ...ingoreProperties) {
        try {
            T t = clazz.newInstance();
            BeanCopier.copy(this, t, BeanCopier.CopyStrategy.IGNORE_NULL, BeanCopier.NoMatchingRule.IGNORE,ingoreProperties);
            return t;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static <T, S extends AbstractEntity> List<T> to(List<S> list, Class<T> clazz) {
        if (list == null || list.isEmpty()) {
            return Lists.newArrayList();
        }
        List<T> ts = new ArrayList<>(list.size());
        for (S s : list) {
            ts.add(s.to(clazz));
        }
        return ts;
    }

    /**
     * 转换Page对象中的集合类型S为T
     */
    public static <S extends AbstractEntity, T> Page<T> to(Page<S> page, Class<T> clazz) {
        return Pages.map(page, clazz);
    }

    /**
     * 把实体对象Set转换为目标对象Set
     */
    public static <T, S extends AbstractEntity> Set<T> to(Set<S> set, Class<T> clazz) {
        if (set == null || set.isEmpty()) {
            return Sets.newHashSet();
        }
        Set<T> ts = new HashSet<>(set.size());
        for (S s : set) {
            ts.add(s.to(clazz));
        }
        return ts;
    }

    @Transient
    public boolean isNew() {
        return null == getId();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (!(o instanceof AbstractEntity))
            return false;

        AbstractEntity that = (AbstractEntity) o;

        return id != null ? id.equals(that.id) : that.id == null;
    }

    @Override
    public int hashCode() {
        return id != null ? id.hashCode() : 0;
    }

    @Override
    public String toString() {
        return ToString.toString(this);
    }

}
